﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;

using MicroDb.Basis;
using MicroDb.Options;

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;

namespace MicroDb.Generate
{
    public class Generator
    {

        private readonly IServiceProvider serviceProvider;

        private readonly MicroDbProvider microDbProvider;

        private readonly TableExecute tableExecute;

        public readonly string dbName;

        public Dictionary<string, EnumObj> Enums { get; set; } = new Dictionary<string, EnumObj>();

        public List<Table> Tables { get; set; }

        public readonly Dictionary<string, string> attrsMapping = new Dictionary<string, string>(){
         {"***","Newtonsoft.Json.JsonIgnore"},
         {"*extend*","Newtonsoft.Json.JsonExtensionData"},
         {"*array*","Newtonsoft.Json.JsonConverter(typeof(ArrayJsonConverter))"},
         {"*hidesome*","Newtonsoft.Json.JsonConverter(typeof(DesensitizeConverter))"},
         {"*intarray*","Newtonsoft.Json.JsonConverter(typeof(ArrayJsonConverter),typeof(int))"},
         {"*longarray*","Newtonsoft.Json.JsonConverter(typeof(ArrayJsonConverter),typeof(long))"},
         {@"(\{.*\})","Newtonsoft.Json.JsonConverter(typeof(AutoCachePaddingConverter), \"{*}\")"},
        };

        public Generator(string connectionStrings, string dbSource, Dictionary<string, string> attrsMapping)
        {
            if (attrsMapping != null)
            {
                this.attrsMapping = attrsMapping;
            }
            var services = new ServiceCollection();
            services.AddOptions();
            services.AddSingleton<IConfigureOptions<MicroDbOptions>>(s => new ConfigureOptions(connectionStrings, dbSource));
            services.AddMicroDb<PostgreSql.NpgSqlFactory>();
            services.AddMicroDb<MySql.MySqlFactory>();
            serviceProvider = services.BuildServiceProvider();
            microDbProvider = MicroDbFactory.CreateProvider(serviceProvider, dbSource);
            tableExecute = microDbProvider.CreateTableExecute();
            dbName = tableExecute.GetCurrentDb(microDbProvider.DbOptions);
        }

        public void Init()
        {
            Tables = tableExecute.GetTables(dbName).Select(info => Create(info)).ToList();
        }

        private Table Create(TableInfo info)
        {
            var codeFirst = tableExecute.GetTableCodeFirst(dbName, info.TableName);
            var cols = tableExecute.GetColumns(dbName, info.TableName);
            var tab = new Table
            {
                Name = BigCamelCaseName(info.TableName),
                TableName = info.TableName,
                DBSource = microDbProvider.DbOptions.DbSource,
                Comment = info.Comment,
                Columns = cols.Select(col => ToColumn(col)).ToArray(),
                CodeFirstXml = codeFirst,
                Attributes = new[] { "Serializable", $"System.DbSource(\"{microDbProvider.DbOptions.DbSource}\")" }
            };
            tab.GetNameSpaces();
            return tab;
        }

        /// <summary>
        /// 原始值类型
        /// </summary>
        /// <param name="type"></param>
        /// <returns></returns>
        private bool IsValueType(string type)
        {
            switch (type)
            {
                case "bool":
                case "byte":
                case "sbyte":
                case "short":
                case "ushort":
                case "int":
                case "uint":
                case "long":
                case "ulong":
                case "decimal":
                case "float":
                case "double":
                case "DateTime":
                case "DateTimeOffset":
                case "TimeSpan":
                    return true;
                default:
                    return false;
            }
        }

        private Column ToColumn(ColumnInfo info)
        {
            if (string.IsNullOrEmpty(info.DataType) && info.DbType == "json")
            {
                info.DataType = "Newtonsoft.Json.Linq.JToken";
            }
            var col = new Column
            {
                ColumnName = info.ColumnName,
                Name = BigCamelCaseName(info.ColumnName),
                Comment = info.Comment,
                DataType = info.DataType,
                DbType = info.DbType,
                Attributes = new string[0],
                CodeFirstXml = info.CodeFist
            };

            var attrs = new List<string>
            {
                $"{(info.IsPrimaryKey ? "Primary" : "Column")}{AttributePs(info)}"
            };
            ResolveComment(col, attrs);
            if (string.IsNullOrEmpty(col.DataType))
            {
                col.DataType = "string";
            }
            if (info.IsNullable && IsValueType(col.DataType))
            {
                col.DataType += "?";
            }
            col.Attributes = attrs.ToArray();
            return col;
        }

        private string AttributePs(ColumnInfo info)
        {
            List<string> strs = new List<string>();
            if (info.DataType == "string")
            {
                strs.Add("Length = " + info.DateLength);
            }
            if (info.IsIncrement)
            {
                strs.Add("Automatic = true");
            }
            if (strs.Count == 0)
                return "";
            return $"({string.Join(", ", strs)})";
        }

        /// <summary>
        /// 解析注释
        /// </summary>
        /// <param name="comment"></param>
        private void ResolveComment(Column column, List<string> attrs)
        {
            if (string.IsNullOrEmpty(column.Comment))
            {
                return;
            }
            var comment = column.Comment;
            comment = BreakToSpace(comment);

            ResolveEnum(column, attrs, ref comment);

            foreach (var item in attrsMapping)
            {
                if (item.Value.Contains("{*}"))
                {
                    var matchout = Regex.Match(comment, item.Key);
                    if (matchout.Success)
                    {
                        comment = comment.Replace(matchout.Value, "");
                        var value = matchout.Value.Trim(new char[] { '{', '}' }).Trim();
                        if (!string.IsNullOrEmpty(value))
                        {
                            attrs.Add(item.Value.Replace("{*}", value));
                        }
                    }
                }
                else if (comment.Contains(item.Key))
                {
                    comment = comment.Replace(item.Key, "").Trim();
                    attrs.Add(item.Value);
                }
            }

            if (column.Comment.Contains("*extend*") || column.Comment.Contains("*jobject*"))
            {
                column.DataType = "Newtonsoft.Json.Linq.JObject";
            }
            if (column.Comment.Contains("*jarray*"))
            {
                column.DataType = "Newtonsoft.Json.Linq.JArray";
            }

            //取第一段文字作为显示名称
            var displayName = comment.Split(new char[] { ',', '，', '、', ' ' })[0].Trim();

            if (!string.IsNullOrEmpty(displayName))
            {
                attrs.Insert(0, $"System.ComponentModel.DisplayName(\"{displayName}\")");
            }
        }


        /// <summary>
        /// 枚举解析
        /// </summary>
        private void ResolveEnum(Column column, List<string> attrs, ref string comment)
        {
            // 枚举 表达式 [...]
            var match = Regex.Match(comment, @"(\[.*\])|(【.*】)");
            if (match.Success)
            {
                comment = comment.Replace(match.Value, "");
                string emstr = match.Value.Trim(new char[] { '[', ']', '【', '】' });

                var enumAttrs = new string[0];
                if (emstr.Contains("#flags"))
                {
                    emstr = emstr.Replace("#flags", "");
                    enumAttrs = new[] { "Flags" };
                }
                column.HasEnum = true;
                column.EnumName = column.Name + "Enum";
                attrs.Add($"Newtonsoft.Json.JsonConverter(typeof(AutoEnumConverter), typeof({column.EnumName}))");
                var items = emstr
                      .Split(new char[] { ',', '，', '、' }
                      , StringSplitOptions.RemoveEmptyEntries)
                      .Select(str => BuilderEnum(str))
                      .ToList();
                if (items == null || items.Count == 0)
                {
                    return;
                }

                var displayName = comment.Split(new char[] { ',', '，', '、', ' ' })[0].Trim();
                Enums[column.EnumName] = new EnumObj
                {
                    Name = column.EnumName,
                    Comment = displayName,
                    Items = items,
                    Attributes = enumAttrs
                };
            }
        }
        private EnumItem BuilderEnum(string str)
        {
            if (str == null || str.Length <= 0) return null;

            var en = new EnumItem();

            var matchColor = Regex.Match(str, @"(\{.*\})");
            if (matchColor.Success)
            {
                str = str.Replace(matchColor.Value, "");
                en.Color = matchColor.Value.Trim(new char[] { '{', '}' }).Trim();
            }

            var es = str.Split(new char[] { '=' }, StringSplitOptions.RemoveEmptyEntries);
            en.Name = es[0].Trim();
            if (str.Contains('='))
            {
                if (es.Length > 1)
                {
                    if (int.TryParse(es[1], out int value))
                    {
                        en.Value = value;
                    }
                    else
                    {
                        en.Description = es[1];
                    }
                }
                if (es.Length > 2)
                {
                    en.Description = es[2];
                }
            }
            return en;
        }


        public static string BreakToSpace(string str)
        {
            if (str == null) return null;
            return Regex.Replace(str, @"(\n)|(\r\n)", " ").Trim();
        }


        public static string BigCamelCaseName(string name)
        {
            string[] names = name.Split('_');
            string result = "";
            for (int i = 0; i < names.Length; i++)
            {
                result += names[i][0].ToString().ToUpperInvariant() + names[i].Substring(1);
            }
            return result;
        }

        public static string BuilderAttrs(string[] attrs, string tabs)
        {
            return string.Join("\r\n", attrs.Select(a => $"{tabs}[{a}]").ToArray());
        }

        public static string BuilderXmlDoc(Dictionary<string, string> codeFirstXml, string tabs)
        {
            return string.Join("", codeFirstXml.Select(item => BuilderXmlDoc(item.Value, tabs, item.Key)));
        }

        private static readonly char[] tsfh = new[] { '\n', '\t', '/', '\\', '<', '>' };
        public static string BuilderXmlDoc(string doc, string tabs, string docName = "summary")
        {
            if (string.IsNullOrEmpty(doc)) return "";
            StringBuilder sb = new StringBuilder();
            sb.AppendFormat("{0}/// <{1}>\r\n", tabs, docName);

            if (docName == "summary" && !tsfh.Any(f => doc.Contains(f)))
            {
                sb.AppendFormat("{0}/// {1}\r\n", tabs, doc);
            }
            else
            {
                var str = "";
                var docs = doc.Split(new char[] { '\n' }, StringSplitOptions.RemoveEmptyEntries);
                if (docs.Length == 1)
                {
                    str = docs[0];
                }
                else
                {
                    for (int i = 0; i < docs.Length; i++)
                    {
                        if (i == 0)
                        {
                            str += docs[i].TrimEnd('\r') + (i == 0 ? "\r\n" : "");
                        }
                        else
                        {
                            str += tabs + "/// " + docs[i].TrimEnd('\r') + (i == docs.Length - 1 ? "" : "\r\n");
                        }
                    }
                }
                sb.AppendFormat("{0}/// <![CDATA[{1}]]>\r\n", tabs, str);
            }
            sb.AppendFormat("{0}/// </{1}>\r\n", tabs, docName);
            return sb.ToString();
        }
        public static string BuilderEnumString(List<EnumItem> enums)
        {
            List<string> sb = new List<string>();
            foreach (var en in enums)
            {
                string str = "";
                if (!string.IsNullOrEmpty(en.Description))
                {
                    str += string.Format("/// <summary>\r\n        /// {0}\r\n        /// </summary>\r\n        [Description(\"{0}\")]\r\n        ", en.Description);
                }
                if (!string.IsNullOrEmpty(en.Color))
                {
                    str += string.Format("[Color(\"{0}\")]\r\n        ", en.Color);
                }
                str += en.Name + (en.Value.HasValue ? (" = " + en.Value) : "");
                sb.Add(str);
            }
            return string.Join(",\r\n        ", sb);
        }
        public static ILookup<string, Table> Group(List<Table> tables)
        {
            return tables
                .GroupBy(a => Regex.Match(a.Name, "^([A-Z][a-z]+)").Value)
                .Select(a =>
                {
                    var list = a.ToList();
                    if (a.Count() > 1)
                    {
                        list = a.Where(b => !Regex.Match(b.Name, "^([A-Z][a-z]+[A-Z][a-z]+[A-Z][a-z])").Success)
                        .ToList();
                    }
                    return list;
                }).SelectMany(a => a)
                .ToLookup(a => Regex.Match(a.Name, "^([A-Z][a-z]+)").Value);
        }

        public static string UseNameSpaces(string[] namespaces, params string[] appends)
        {

            if (namespaces != null)
            {
                appends = appends.Concat(namespaces).ToArray();
            }
            return string.Join("\r\n", appends.Where(a => !string.IsNullOrEmpty(a)).Distinct().Select(a => $"using {a};"));
        }

        public static Generator CreateGenerator(Dictionary<string, string> attrsMapping, string dbSource, string path, params string[] fileNames)
        {
            string connectionString = null;
            foreach (var fileName in fileNames)
            {
                string filepath = Path.Combine(path, fileName);
                if (!File.Exists(filepath)) continue;
                string json = File.ReadAllText(filepath);
                if (string.IsNullOrEmpty(json))
                {
                    continue;
                }
                var configJson = Newtonsoft.Json.JsonConvert.DeserializeObject<ConfigJson>(json);
                if (configJson?.connectionStrings == null || !configJson.connectionStrings.ContainsKey(dbSource))
                {
                    continue;
                }
                connectionString = configJson.connectionStrings[dbSource];
            }
            if (string.IsNullOrEmpty(connectionString))
            {
                throw new Exception("未配置链接字符串：" + dbSource);
            }
            return new Generator(connectionString, dbSource, attrsMapping);
        }
    }
}
